package com.lzw.springbootredis.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;

import java.util.*;

/**
 * @program: springboot-redis
 * @description: RedisController
 * @author: lizhiwei
 * @create: 2019-06-12 17:45
 **/
@Controller
@RequestMapping("/redis")
public class RedisController {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/stringAndHash")
    @ResponseBody
    public Map<String,String> testStringAndHash(){
        redisTemplate.opsForValue().set("keytest","valuetest");
        //注意这里使用了JDK的序列化器，所以Redis保存时不是整数，不能运算
        redisTemplate.opsForValue().set("int_key","1");
        stringRedisTemplate.opsForValue().set("int","1");
        //使用运算
        stringRedisTemplate.opsForValue().increment("int",1);

        //获取底层Jedis连接
        Jedis jedis = (Jedis) stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
        //减一操作，这个命令RedisTemplate不支持，所以我先获取底层的连接再操作
        jedis.decr("int");
        Map<String,String> hash = new HashMap<String,String>();
        hash.put("field1","value1");
        hash.put("field2","value2");
        //存入一个散列数据类型
        stringRedisTemplate.opsForHash().putAll("hash",hash);
        stringRedisTemplate.opsForHash().put("hash","field3","value3");
        //绑定散列操作的key，这样可以连续对同一个散列数据进行操作
        BoundHashOperations hashOperations = stringRedisTemplate.boundHashOps("hash");
        hashOperations.delete("field1","field2");
        //新增一个字段
        hashOperations.put("field4","value5");
        Map<String,String> map = new HashMap<String,String>();
        map.put("success","true");
        return map;
    }

    @RequestMapping("/list")
    @ResponseBody
    public Map<String,Object> testList(){
        //插入两个列表，注意它们在链表的顺序
        //列表从左到右顺序为v10，v8,v6,v4,v2
        stringRedisTemplate.opsForList().leftPushAll("list1","v2","v4","v6","v8","v10");
        //列表从左到右顺序为v1,v2,v3,v4,v5,v6
        stringRedisTemplate.opsForList().rightPushAll("list2","v1","v2","v3","v4","v5","v6");

        //绑定list2链表操作
        BoundListOperations listOperations = stringRedisTemplate.boundListOps("list2");
        //从右边弹出一个成员
        listOperations.rightPop();
        //获取定位元素，Redis从0开始计算，这里的值为v2
        Object result2 = listOperations.index(1);
        //从左边插入一个列表
        listOperations.leftPush("v0");
        //求链表长度
        Long size = listOperations.size();
        //求链表下标区间成员，整个链表下标范围0到size-1,这里娶不到最后一个元素
        List elements = listOperations.range(0,size-2);
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("success","true");
        return map;

    }
    @RequestMapping("/set")
    @ResponseBody
    public Map<String,Object> testSet(){
        //注意：这里的v1重复两次，因为集合不允许重复，所以只是插入5个成员到集合中
        stringRedisTemplate.opsForSet().add("set1","v1","v1","v2","v3","v4","v5");
        stringRedisTemplate.opsForSet().add("set2","v2","v4","v6","v8");
        //绑定set1集合操作
        BoundSetOperations setOperations = stringRedisTemplate.boundSetOps("set1");
        //增加两个元素
        setOperations.add("v6","v7");
        setOperations.remove("v1","v7");
        //返回所有元素
        Set set1 = setOperations.members();
        //求成员数
        Long size = setOperations.size();

        //求交集
        Set inter = setOperations.intersect("set2");
        //求差集，
        Set diff = setOperations.diff("set2");
        //求差集，并且用新的集合保存
        setOperations.diffAndStore("set2","diff");

        //求并集
        Set union = setOperations.union("set2");
        //求并集，并且用新集合union保存
        setOperations.unionAndStore("set2","diff");
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("success","true");
        return map;
    }

    @RequestMapping("/zset")
    @ResponseBody
    public Map<String,Object> testZset(){
        Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
        for (int i=1;i<=9;i++){
            //分数
            double score = i*0.1;
            //创建一个TypedTuple对象，存入值和分数
            ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<String>("value"+i,score);
            typedTupleSet.add(typedTuple);
        }
        //往有序集合里面插入元素
        stringRedisTemplate.opsForZSet().add("zset1",typedTupleSet);
        //绑定zset1有序集合操作
        BoundZSetOperations<String,String> zSetOperations = stringRedisTemplate.boundZSetOps("zset1");
        //增加一个元素
        zSetOperations.add("value10",0.26);
        Set<String> setRange = zSetOperations.range(1,6);
        //按分数排序获取有序集合
        Set<String> setScore = zSetOperations.rangeByScore(0.2,0.6);
        //定义值范围
        RedisZSetCommands.Range range = new RedisZSetCommands.Range();
        range.gt("value3");
        range.lte("value8");
        //按值排序，请注意这个排序是按字符串排序
        Set<String> setLex = zSetOperations.rangeByLex(range);
        //删除元素
        zSetOperations.remove("value9","value2");
        //求分数
        Double score = zSetOperations.score("value8");
        //在下标区间下，按分数排序，同时返回value和score
        Set<ZSetOperations.TypedTuple<String>> rangSet = zSetOperations.rangeWithScores(1,6);
        //在分数区间下，按分数排序，同时返回value和score
        Set<ZSetOperations.TypedTuple<String>> scoreSet = zSetOperations.rangeByScoreWithScores(1,6);
        //从大到小排序
        Set<String> reverseSet = zSetOperations.reverseRange(2,8);
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("success","true");
        return map;
    }

    @RequestMapping("/multi")
    @ResponseBody
    //注意redis的事务机制是当监控的可以发送变化后将取消事务，否则执行事务，当执行事务中发生异常后，事务不会回滚，依然执行这个是和数据库不同的地方
    public Map<String,Object> testMulti(){
        redisTemplate.opsForValue().set("key1","value1");
        List list = (List)redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                //设置要监控key1
                redisOperations.watch("key1");
                //开启事务，在exec命令执行前，全部都只是进入队列
                redisOperations.multi();
                redisOperations.opsForValue().set("key2","value2");
                //redisOperations.opsForValue().increment("key1",1);
                //获取值将为null，因为redis只是把命令放到队列中
                Object value2 = redisOperations.opsForValue().get("key2");
                System.out.println("命令在队列中，所以value2为空，"+value2);
                redisOperations.opsForValue().set("key3","value3");
                Object value3 = redisOperations.opsForValue().get("key3");
                System.out.println("命令在队列中，所以value3为空，"+value3);

                //执行exec命令，将先判断key1是否在监控后被修改过，如果是则不执行事务，否则执行事务
                return redisOperations.exec();
            }
        });
        System.out.println(list);
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("success","true");
        return map;
    }

    @RequestMapping("/pipeline")
    @ResponseBody
    public Map<String,Object> testPipeline(){
        Long start = System.currentTimeMillis();
        List list = (List)redisTemplate.executePipelined(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                for (int i=1;i<=100000;i++){
                    redisOperations.opsForValue().set("pipeline_"+i,"value_"+i);
                    String value = (String)redisOperations.opsForValue().get("pipeline_"+i);
                    if (i == 100000) {
                        System.out.println("命令只是进入队列，所以值为空：" + value);
                    }
                }
                return null;
            }
        });
        Long end = System.currentTimeMillis();
        System.out.println("耗时："+ (end -start) + "毫秒");
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("success","true");
        return map;
    }

    @RequestMapping("/listener")
    @ResponseBody
    //http://localhost:8080/redis//listener?message=hello
    public Map<String,Object> testListenerMessage(String message){
        redisTemplate.convertAndSend("topic1",message);
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("success","true");
        return map;
    }

    @RequestMapping("/lua")
    @ResponseBody
    public Map<String,Object> testLua(){
        DefaultRedisScript<String> rs = new DefaultRedisScript<String>();
        //设置脚本
        rs.setScriptText("return 'hello Redis'");
        //定义返回类型。注意：如果没有这个定义，spring不会返回结果
        rs.setResultType(String.class);
        RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
        //执行Lua脚本
        String str = (String) redisTemplate.execute(rs,stringRedisSerializer,stringRedisSerializer,null);
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("success","true");
        return map;
    }

    @RequestMapping("/lua2")
    @ResponseBody
    //http://localhost:8080/redis/lua2?key1=a&&key2=b&value1=lizhiwei&value2=lizhiwei
    public Map<String,Object> testLua2(String key1,String key2,String value1,String value2){
        DefaultRedisScript<Long> rs = new DefaultRedisScript<Long>();
        //设置脚本
        String lua = "redis.call('set',KEYS[1]，ARGV[1]) \n" +
                "redis.call('set',KEYS[2]，ARGV[2]) \n" +
                "local str1 = redis.call('get', KEYS[1]) \n" +
                "local str2 = redis.call('get',KEYS[2]) \n" +
                "if str1 == str2 then \n" +
                "return 1 \n" +
                "end \n" +
                "return 0 \n";
        System.out.println(lua);
        rs.setScriptText(lua);
        //定义返回类型。注意：如果没有这个定义，spring不会返回结果
        rs.setResultType(Long.class);
        RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
        //定义key参数
        List<String> keyList = new ArrayList<>();
        keyList.add(key1);
        keyList.add(key2);
        //执行Lua脚本
        Long result = (Long) redisTemplate.execute(rs,stringRedisSerializer,stringRedisSerializer,keyList,value1,value2);
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("success","true");
        return map;
    }
}
